﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Xml.Serialization;

namespace Chromatics
{
    /// <summary>
    /// Class to load needed files
    /// </summary>
    public class DataLoader
    {
        /// <summary>
        /// Connection string to application database;
        /// </summary>
        private const string DBConnectionString = @"isostore:/Database.sdf";

        private List<Graph> Easy;
        private List<Graph> Medium;
        private List<Graph> Hard;
        private List<Level> LevelInfos;

        /// <summary>
        /// Logic to provide singleton pattern
        /// </summary>
        #region Singleton logic

        private static DataLoader instance = null;

        private DataLoader()
        {
            XmlSerializer SerializerObj = new XmlSerializer(typeof(Graph));
            Easy = new List<Graph>();
            for (int i = 0; i < this.LevelsCount(Difficulty.Easy); ++i)
            {
                string fileName = "Levels/e" + (i + 1).ToString() + ".xml" ;
                Stream ReadFileStream = (Application.GetResourceStream(new Uri(fileName, UriKind.Relative))).Stream;
                Graph toRet = (Graph)SerializerObj.Deserialize(ReadFileStream);
                ReadFileStream.Close();
                Easy.Add(toRet);
            }
            Medium = new List<Graph>();
            for (int i = 0; i < this.LevelsCount(Difficulty.Medium); ++i)
            {
                string fileName = "Levels/m" + (i + 1).ToString() + ".xml";
                Stream ReadFileStream = (Application.GetResourceStream(new Uri(fileName, UriKind.Relative))).Stream;
                Graph toRet = (Graph)SerializerObj.Deserialize(ReadFileStream);
                ReadFileStream.Close();
                Medium.Add(toRet);
            }
            Hard = new List<Graph>();
            for (int i = 0; i < this.LevelsCount(Difficulty.Hard); ++i)
            {
                string fileName = "Levels/h" + (i + 1).ToString() + ".xml";
                Stream ReadFileStream = (Application.GetResourceStream(new Uri(fileName, UriKind.Relative))).Stream;
                Graph toRet = (Graph)SerializerObj.Deserialize(ReadFileStream);
                ReadFileStream.Close();
                Hard.Add(toRet);
            }
            this.LoadAllLevelsInfo();

        }

        public static DataLoader Instance
        {
            get
            {
                if (instance == null)

                    instance = new DataLoader();
                return instance;
            }
        }
        #endregion

        private void LoadAllLevelsInfo()
        {
            //Loads all info about all levels
            using (UserSettingsDataContext UsrDB = new UserSettingsDataContext(DBConnectionString))
            {
                if (UsrDB.DatabaseExists() == false)
                {
                    this.CreateDefaultDatabase();
                }
                LevelInfos = UsrDB.Levels.Select(a => new Level() { Number = a.Number, Difficulty = StrToEnumConverter.strToDiff(a.Difficulty), Status = StrToEnumConverter.strToStat(a.Status) }).ToList();
            }
        }

        /// <summary>
        /// Method wich loads level from memory
        /// </summary>
        /// <param name="number">number of level</param>
        /// <param name="Difficulty">name of Difficulty</param>
        /// <returns>loaded graph</returns>
        public Graph LoadGraph(int number, Difficulty difficulty)
        {
            if (difficulty == Difficulty.Easy)
                return Easy[number - 1];
            else if (difficulty == Difficulty.Medium)
                return Medium[number - 1];
            else if (difficulty == Difficulty.Hard)
                return Hard[number - 1];
            else
                throw new Exception("There are no such level to load!");
        }


        /// <summary>
        /// Resets all levels to close, first to open
        /// </summary>
        public void ResetAllLevels()
        {
            UserSettings setings = this.LoadUserSettings();
            this.CreateDefaultDatabase();
            this.LoadAllLevelsInfo();
            this.SaveUserSettings(setings);
        }

        /// <summary>
        /// Creates default database
        /// </summary>
        private void CreateDefaultDatabase()
        {
            using (UserSettingsDataContext DB = new UserSettingsDataContext(DBConnectionString))
            {
                if (DB.DatabaseExists())
                {
                    DB.DeleteDatabase();
                }
                DB.CreateDatabase();
                UserSetting defSetting = new UserSetting() { ID = 1, Sound = true, Music = true };
                DB.UserInfo.InsertOnSubmit(defSetting);
                DB.SubmitChanges();
                //levels
                int id = 1;
                for (int i = 0; i < this.LevelsCount(Difficulty.Easy); ++i)
                {
                    Levels newElem = new Levels() { ID = id++, Difficulty = "Easy", Number = i + 1, Status = "Closed" };
                    if (i == 0)
                        newElem.Status = "Open";
                    DB.Levels.InsertOnSubmit(newElem);
                }
                for (int i = 0; i < this.LevelsCount(Difficulty.Medium); ++i)
                {
                    Levels newElem = new Levels() { ID = id++, Difficulty = "Medium", Number = i + 1, Status = "Closed" };
                    DB.Levels.InsertOnSubmit(newElem);
                }
                for (int i = 0; i < this.LevelsCount(Difficulty.Hard); ++i)
                {
                    Levels newElem = new Levels() { ID = id++, Difficulty = "Hard", Number = i + 1, Status = "Closed" };
                    DB.Levels.InsertOnSubmit(newElem);
                }
                DB.SubmitChanges();
            }
        }

        /// <summary>
        /// Method wich loads user settings
        /// </summary>
        /// <returns>user settings</returns>
        public UserSettings LoadUserSettings()
        {
            using (UserSettingsDataContext DB = new UserSettingsDataContext(DBConnectionString))
            {
                //if (DB.DatabaseExists() == false)
                //{
                //    this.CreateDefaultDatabase();
                //}
                UserSetting userSettings = (from Usr in DB.UserInfo where Usr.ID == 1 select Usr).FirstOrDefault();

                return new UserSettings() { Sound = userSettings.Sound, Music = userSettings.Music};
            }
        }

        /// <summary>
        /// Method wich saves user settings
        /// </summary>
        /// <returns>user settings</returns>
        public void SaveUserSettings(UserSettings setts)
        {
            UserSetting setting = new UserSetting() { ID = 1, Sound = setts.Sound, Music = setts.Music };
            using (UserSettingsDataContext DB = new UserSettingsDataContext(DBConnectionString))
            {
                //if (DB.DatabaseExists() == false)
                //{
                //    this.CreateDefaultDatabase();
                //}
                //else
                //{
                    //remove old
                    IQueryable<UserSetting> UserSetting = from Usr in DB.UserInfo where Usr.ID == 1 select Usr;
                    UserSetting ToRemove = UserSetting.FirstOrDefault();
                    if(ToRemove != null)
                        DB.UserInfo.DeleteOnSubmit(ToRemove);
                    DB.SubmitChanges();
                //}
                //add new
                DB.UserInfo.InsertOnSubmit(setting);
                DB.SubmitChanges();
            }
        }

        /// <summary>
        /// Method wich loads  info about level
        /// </summary>
        /// <param name="number"> Number of level</param>
        /// <param name="diff"> difficulty of level</param>
        /// <returns> level information </returns>
        public Level LoadLevelInfo(int number, Difficulty diff)
        {
            int pos;
            if (diff == Difficulty.Easy)
                pos = 0;
            else if (diff == Difficulty.Medium)
                pos = this.LevelsCount(Difficulty.Easy);
            else
                pos = this.LevelsCount(Difficulty.Easy) + this.LevelsCount(Difficulty.Medium);
            pos += number - 1;
            Level toRet = LevelInfos[pos];
            if (toRet.Difficulty != diff || toRet.Number != number)
                throw new IndexOutOfRangeException("Developers are lozes!");
            return toRet;
        }

        /// <summary>
        /// Method wich save game progress of user 
        /// </summary>
        public void SaveLevelInfo(Level info)
        {
            //local saving
            int pos;
            if (info.Difficulty == Difficulty.Easy)
                pos = 0;
            else if (info.Difficulty == Difficulty.Medium)
                pos = this.LevelsCount(Difficulty.Easy);
            else
                pos = this.LevelsCount(Difficulty.Easy) + this.LevelsCount(Difficulty.Medium);
            pos += info.Number - 1;
            if (LevelInfos[pos].Difficulty != info.Difficulty || LevelInfos[pos].Number != info.Number)
                throw new IndexOutOfRangeException("Developers are lozes");
            LevelInfos[pos] = info;
            //db saving
            string diff = string.Empty;
                switch(info.Difficulty)
                {
                    case Difficulty.Easy:
                    diff = "Easy";
                    break;
                    case Difficulty.Medium:
                    diff = "Medium";
                    break;
                    case Difficulty.Hard:
                    diff = "Hard";
                    break;
                    default:
                    diff = "GameOver";
                    break;
                }
            using (UserSettingsDataContext DB = new UserSettingsDataContext(DBConnectionString))
            {
                int id = -1;
                //if (DB.DatabaseExists() == false)
                //{
                //    this.CreateDefaultDatabase();
                //}
                //else
                //{
                    //remove old
                    IQueryable<Levels> levels = from Lvl in DB.Levels where (Lvl.Number == info.Number && Lvl.Difficulty == diff) select Lvl;
                    Levels ToRemove = levels.FirstOrDefault();
                    id = ToRemove.ID;
                    if(ToRemove != null)
                        DB.Levels.DeleteOnSubmit(ToRemove);
                    DB.SubmitChanges();
                //}
                //add new
                string status;
                //{ Open, Closed, Star0, Star1, Star2, Star3};
                switch(info.Status)
                {
                    case LevelStatus.Open:
                        status = "Open";
                    break;
                    case LevelStatus.Closed:
                    status = "Closed";
                    break;
                    case LevelStatus.Star0:
                    status = "Star0";
                    break;
                        case LevelStatus.Star1:
                    status = "Star1";
                    break;
                        case LevelStatus.Star2:
                    status = "Star2";
                    break;
                        case LevelStatus.Star3:
                    status = "Star3";
                    break;
                    default:
                    status = "Closed";
                    break;
                }
                Levels toAdd;
                if(id != -1)
                    toAdd = new Levels() { Difficulty = diff, Number = info.Number, Status = status, ID = id};
                else
                    toAdd = new Levels() { Difficulty = diff, Number = info.Number, Status = status};
                DB.Levels.InsertOnSubmit(toAdd);
                DB.SubmitChanges();
            }
        }

        /// <summary>
        /// Gets number of levels with current difficulty
        /// </summary>
        /// <param name="difficulty">needed difficulty</param>
        /// <returns></returns>
        public int LevelsCount(Difficulty difficulty)
        {
            switch (difficulty)
            {
                case Difficulty.Easy:
                    return 15;

                case Difficulty.Medium:
                    return 15;

                case Difficulty.Hard:
                    return 15;

                default:
                    return -1;
            }
        }

        /// <summary>
        /// Method which rescan all stars and open new level if it exist
        /// </summary>
        public void RecalcLevelDependencies()
        {   
            int pos = 0;
            int summStars = 0;
            //search first closed level
            while (LevelInfos[pos].Status != LevelStatus.Closed)
            {
                int stars = 0;
                if (LevelInfos[pos].Status == LevelStatus.Star1)
                    stars = 1;
                if (LevelInfos[pos].Status == LevelStatus.Star2)
                    stars = 2;
                if (LevelInfos[pos].Status == LevelStatus.Star3)
                    stars = 3;
                summStars += stars;
                ++pos;
                if (pos == LevelInfos.Count())
                    return;
            }
            int percents = (summStars * 100) / (pos * 3);
            int needPercents = 100;
            while (true)
            {
                if (LevelInfos[pos].Difficulty == Difficulty.Easy)
                    needPercents = 75;
                if (LevelInfos[pos].Difficulty == Difficulty.Medium)
                    needPercents = 85;
                if (LevelInfos[pos].Difficulty == Difficulty.Hard)
                    needPercents = 95;
                if (percents >= needPercents)
                {
                    LevelInfos[pos].Status = LevelStatus.Open;
                    this.SaveLevelInfo(LevelInfos[pos]);
                }
                else
                {
                    break;
                }
                if (pos + 1 == LevelInfos.Count())
                    return;
                ++pos;
                percents = (summStars * 100) / (pos * 3);
            }
        }

        /// <summary>
        /// Method which calc count of avaliable stars
        /// </summary>
        public int AvaliableStars()
        {
            int pos = 0;
            int summStars = 0;
            //calc all avaliable stars
            while (LevelInfos[pos].Status != LevelStatus.Closed)
            {
                int stars = 0;
                if (LevelInfos[pos].Status == LevelStatus.Star1)
                    stars = 1;
                if (LevelInfos[pos].Status == LevelStatus.Star2)
                    stars = 2;
                if (LevelInfos[pos].Status == LevelStatus.Star3)
                    stars = 3;
                summStars += stars;
                ++pos;
                if (pos == LevelInfos.Count())
                    break;
            }
            return summStars;
        }

        /// <summary>
        /// Method which calc count of present and needed levels to unlock
        /// </summary>
        public int NeededStars(int number, Difficulty diff)
        {
            int pos = 0;
            double percents = 1;
            if (diff == Difficulty.Easy)
            {
                percents = 0.75;
                pos = number - 1;
            }
            else if (diff == Difficulty.Medium)
            {
                percents = 0.85;
                pos = this.LevelsCount(Difficulty.Easy) + number - 1;
            }
            else //hard
            {
                pos = this.LevelsCount(Difficulty.Easy) + this.LevelsCount(Difficulty.Medium) + number - 1;
                percents = 0.95;
            }
            return (int)Math.Ceiling((pos*3.0)*percents);
        }

    }
}
